package utils
{
	import flash.display3D.Context3D;
	import flash.display3D.Context3DBlendFactor;
	import flash.display3D.Context3DCompareMode;
	import flash.display3D.Context3DProgramType;
	import flash.display3D.Context3DTriangleFace;
	import flash.display3D.Context3DVertexBufferFormat;
	import flash.display3D.IndexBuffer3D;
	import flash.display3D.Program3D;
	import flash.display3D.VertexBuffer3D;
	import flash.display3D.textures.Texture;
	import flash.geom.Matrix3D;
	import flash.geom.Vector3D;
	
	import utils.base.MeshBuffer;
	import utils.parser.Stage3DParser;
	import utils.parser.Stage3D_md5mesh_parser;
	import utils.parser.Stage3D_obj_parser;
	import utils.parser.Stage3d_md5_parser;

	public class Entity3D
	{
		// Matrix variables (position, rotation, etc.)
		protected var _x:Number = 0;
		protected var _y:Number = 0;
		protected var _z:Number = 0;
		private var _rotationDegreesX:Number = 0;
		private var _rotationDegreesY:Number = 0;
		private var _rotationDegreesZ:Number = 0;
		protected var _scaleX:Number = 1;
		protected var _scaleY:Number = 1;
		protected var _scaleZ:Number = 1;
		protected const RAD_TO_DEG:Number = 180/Math.PI;
		
		protected var _transform:Matrix3D;
		protected var _transformNeedsUpdate:Boolean;
		private var _valuesNeedUpdate:Boolean;
		
		public var context:Context3D;
		public var shader:Program3D;
		public var texture:Texture;
		public var vertexBuffer:VertexBuffer3D;
		public var indexBuffer:IndexBuffer3D;
		
		public var mesh:Stage3DParser;
		
		// Render modes:
		public var blend_src:String = Context3DBlendFactor.ONE;
		public var blend_dst:String = Context3DBlendFactor.ZERO;
		public var depth_test_mode:String = Context3DCompareMode.LESS;
		public var depth_test:Boolean = true;
		public var culling_mode:String = Context3DTriangleFace.BACK;
		// if this is set entity is "stuck" to another
		public var _following:Entity3D;
		
		public function Entity3D(modeldata:String = null,mycontext:Context3D = null,
								 myshader:Program3D = null,mytexture:Texture = null,modelscale:Number = 1,
								 data_is_zxy:Boolean = true, texture_flip:Boolean = true)
		{
			_transform = new Matrix3D;
			context = mycontext;
			shader = myshader;
			texture = mytexture;
			
			if (modeldata){
				//mesh = new Stage3D_obj_parser(modeldata,context,modelscale, data_is_zxy, texture_flip);
				//mesh = new Stage3D_md5mesh_parser(modeldata,context);
			}
			
		}
		
		public function get transform():Matrix3D
		{
			if(_transformNeedsUpdate)
				updateTransformFromValues();
			return _transform;
		}
		public function set transform(value:Matrix3D):void
		{
			_transform = value;
			_transformNeedsUpdate = false;
			_valuesNeedUpdate = true;
		}
		
		// Position:
		public function set x(value:Number):void
		{
			_x = value;
			_transformNeedsUpdate = true;
		}
		public function get x():Number
		{
			if(_valuesNeedUpdate)
				updateValuesFromTransform();
			return _x;
		}
		
		public function set y(value:Number):void
		{
			_y = value;
			_transformNeedsUpdate = true;
		}
		public function get y():Number
		{
			if(_valuesNeedUpdate)
				updateValuesFromTransform();
			return _y;
		}
		public function set z(value:Number):void
		{
			_z = value;
			_transformNeedsUpdate = true;
		}
		public function get z():Number
		{
			if(_valuesNeedUpdate)
				updateValuesFromTransform();
			return _z;
		}
		
		// Rotation:
		public function set rotationDegreesX(value:Number):void
		{
			_rotationDegreesX = value;
			_transformNeedsUpdate = true;
		}
		public function get rotationDegreesX():Number
		{
			if(_valuesNeedUpdate)
				updateValuesFromTransform();
			return _rotationDegreesX;
		}
		public function set rotationDegreesY(value:Number):void
		{
			_rotationDegreesY = value;
			_transformNeedsUpdate = true;
		}
		public function get rotationDegreesY():Number{
			if(_valuesNeedUpdate)
				updateValuesFromTransform();
			return _rotationDegreesY;
		}
		public function set rotationDegreesZ(value:Number):void
		{
			_rotationDegreesZ = value;
			_transformNeedsUpdate = true;
		}
		
		public function set scaleX(value:Number):void
		{
			_scaleX = value;
			_transformNeedsUpdate = true;
		}
		public function get scaleX():Number
		{
			if(_valuesNeedUpdate)
				updateValuesFromTransform();
			return _scaleX;
		}
		public function set scaleY(value:Number):void
		{
			_scaleY = value;
			_transformNeedsUpdate = true;
		}
		public function get scaleY():Number
		{
			if(_valuesNeedUpdate)
				updateValuesFromTransform();
			return _scaleY;
		}
		public function set scaleZ(value:Number):void
		{
			_scaleZ = value;
			_transformNeedsUpdate = true;
		}
		public function get scaleZ():Number
		{
			if(_valuesNeedUpdate)
				updateValuesFromTransform();
			return _scaleZ;
		}
		
		public function get rotationDegreesZ():Number
		{
			if(_valuesNeedUpdate)
				updateValuesFromTransform();
			return _rotationDegreesZ;
		}
		
		public function updateTransformFromValues():void
		{
			_transform.identity();
			_transform.appendRotation(_rotationDegreesX, Vector3D.X_AXIS);
			_transform.appendRotation(_rotationDegreesY, Vector3D.Y_AXIS);
			_transform.appendRotation(_rotationDegreesZ, Vector3D.Z_AXIS);
			_transform.appendScale(_scaleX, _scaleY, _scaleZ);
			_transform.appendTranslation(_x, _y, _z);
			_transformNeedsUpdate = false;
		}
		public function updateValuesFromTransform():void
		{
			var d:Vector.<Vector3D> = _transform.decompose();
			var position:Vector3D = d[0];
			_x = position.x;
			_y = position.y;
			_z = position.z;
			var rotation:Vector3D = d[1];
			_rotationDegreesX = rotation.x*RAD_TO_DEG;
			_rotationDegreesY = rotation.y*RAD_TO_DEG;
			_rotationDegreesZ = rotation.z*RAD_TO_DEG;
			var scale:Vector3D = d[2];
			_scaleX = scale.x;
			_scaleY = scale.y;
			_scaleZ = scale.z;
			_valuesNeedUpdate = false;
		}
		
		
		
		public function follow(thisentity:Entity3D):void
		{
			_following = thisentity;
		}
		
		public var matrix:Matrix3D = new Matrix3D();
		public var maxMesh:int = 3;
		public function render(view:Matrix3D,projection:Matrix3D):void
		{
			// only render if these are set
			if (!mesh) return;
			if (!context) return;
			if (!shader) return;
			if (!texture) return;
			//Reset our matrix
			matrix.identity();
			matrix.append(transform);
			if (_following) matrix.append(_following.transform);
			matrix.append(view);
			matrix.append(projection);
			// Set the vertex program register vc0 to our model matrix
			// Set the AGAL program
			context.setProgram(shader);
			// Set the fragment program register ts0 to a texture
			context.setTextureAt(0,texture);
			
			context.setBlendFactors(blend_src, blend_dst);
			
			context.setDepthTest(depth_test,depth_test_mode);
			context.setCulling(culling_mode);
			
			var meshlist:Vector.<MeshBuffer> = mesh.meshList;
			var frame:int = Stage3d_md5_parser(mesh).currentFrame;
			if(maxMesh > meshlist.length){
				maxMesh = meshlist.length
			}
			for(var i:int;i<maxMesh;i++){
				var meshbuffer:MeshBuffer = meshlist[i];
				context.setVertexBufferAt(0, meshbuffer.positionsBufferAry[frame],
					0, Context3DVertexBufferFormat.FLOAT_3);
				
				context.setVertexBufferAt(1, meshbuffer.uvBuffer,
					0, Context3DVertexBufferFormat.FLOAT_2);
				
				context.setProgramConstantsFromMatrix(
					Context3DProgramType.VERTEX, 0, matrix, true);
				
				context.drawTriangles(meshbuffer.indexBuffer,
					0, meshbuffer.indexBufferCount);
			}
			/*context.setVertexBufferAt(0, mesh.positionsBuffer,
				0, Context3DVertexBufferFormat.FLOAT_3);
			
			context.setVertexBufferAt(1, mesh.uvBuffer,
				0, Context3DVertexBufferFormat.FLOAT_2);
			
			context.drawTriangles(mesh.indexBuffer,
				0, mesh.indexBufferCount);*/
		}
		
		public function clone():Entity3D
		{
			if(_transformNeedsUpdate)
				updateTransformFromValues();
			var myclone:Entity3D = new Entity3D();
			myclone.transform = this.transform.clone();
			myclone.mesh = this.mesh;
			myclone.texture = this.texture;
			myclone.shader = this.shader;
			myclone.vertexBuffer = this.vertexBuffer;
			myclone.indexBuffer = this.indexBuffer;
			myclone.context = this.context;
			myclone.updateValuesFromTransform();
			return myclone;
		}
		
		
	}
}